前幾天我們學會了控制 3D 空間,但如果只在一個小型的廣場外圍加上四道牆壁,會讓遊戲看起來像是一個封閉的箱子。當鏡頭帶到牆壁外時,甚至會看到破圖的黑色虛空,這絕對不是我們想要的沉浸感。
今天,我們要進行一次架構大逆轉。我們要拔除讓玩家自由旋轉的 OrbitControls,改用 ARPG 經典的「斜向俯視」視角。更重要的是,我們要拆除所有邊界,直接生成一塊極度巨大的像素曠野,確保玩家的螢幕無論怎麼移動,永遠都會被像素地圖給填滿!
【Step 1:拔除控制器與重設攝影機】
首先,刪除專案中所有關於 OrbitControls 的程式碼。我們不再把鏡頭的控制權交給滑鼠,而是由程式碼來掌控。將 PerspectiveCamera 的視野角度 (fov) 調整為 60 度,這是在俯視角下能展現廣度、又不會造成嚴重透視變形的最佳參數。
【Step 2:鋪設擴大的像素地板】
為了填滿螢幕,我們需要極大的地板。原本 PlaneGeometry 的尺寸只有 10x10,現在我們將它一口氣放大十倍,變成 100x100 的巨型平原。
同時,為了不讓像素圖案被拉伸成模糊的災難,必須同步將 floorTexture.repeat.set(100, 100) 設定為 100 次重複,確保每一格石磚都維持完美的銳利顆粒感。
// 2. 載入地板材質
const textureLoader = new THREE.TextureLoader();
const floorTexture = textureLoader.load('/floor.png');
floorTexture.magFilter = THREE.NearestFilter;
floorTexture.wrapS = THREE.RepeatWrapping;
floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(100, 100);
const floorGeo = new THREE.PlaneGeometry(100, 100);
const floorMat = new THREE.MeshStandardMaterial({ map: floorTexture, side: THREE.DoubleSide });
const floor = new THREE.Mesh(floorGeo, floorMat);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
【本日結語】
現在的畫面上是一片無垠的像素平原。這片廣大的留白不是為了空曠,而是為接下來的地圖生成做準備。之後我們將利用圖論演算法或複雜的二維陣列,在這片平原上動態生成錯落的廢墟、石牆與樹木,打造出真正的探索感。
import './style.css';
import * as THREE from 'three';
// 1. 基礎設定 (無 OrbitControls,幽暗背景)
const scene = new THREE.Scene();
scene.background = new THREE.Color('#0a0a14');
const sizes = { width: window.innerWidth, height: window.innerHeight };
// 視野角度設定為 60 度,適合俯視角且不易邊緣變形
const camera = new THREE.PerspectiveCamera(60, sizes.width / sizes.height, 0.1, 1000);
scene.add(camera);
const canvas = document.querySelector('#webgl-canvas');
const renderer = new THREE.WebGLRenderer({ canvas: canvas });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 2. 鋪設無邊際的廣闊大地 (尺寸放大至 100x100)
const textureLoader = new THREE.TextureLoader();
const floorTexture = textureLoader.load('/floor.png');
floorTexture.magFilter = THREE.NearestFilter;
floorTexture.wrapS = THREE.RepeatWrapping;
floorTexture.wrapT = THREE.RepeatWrapping;
// 配合放大的地圖,紋理也重複 100 次,維持像素顆粒感
floorTexture.repeat.set(100, 100);
// 將原本 10x10 的地板,一口氣擴張成 100x100 的巨大曠野
const floorGeo = new THREE.PlaneGeometry(100, 100);
const floorMat = new THREE.MeshStandardMaterial({
map: floorTexture,
side: THREE.DoubleSide
});
const floor = new THREE.Mesh(floorGeo, floorMat);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
// 加入微弱的環境底光
scene.add(new THREE.AmbientLight('#202030', 1.2));
// 3. 基礎渲染迴圈 (固定俯視預覽視角)
function animate() {
requestAnimationFrame(animate);
// 目前還沒加入主角,先將鏡頭固定在場景中央的斜上方
camera.position.set(0, 15, 15);
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
animate();